﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Bouyei.Geo.GeoParser
{
    using Geometries;

    public class WktParser : BaseStringParser
    {
        const string bracketContent = @"(?<=\()[^\(\)]*(?=\))";//只是匹配括号内容
        //const string bracketCC = @"\(.*\)";//第一次括号，包含括号

        public GeoType geometryType { get; private set; }

        static readonly char[] comma = new char[] { ',' };
        static readonly char[] space = new char[] { ' ' };
        readonly Regex reg;

        public WktParser(string wkt)
            : base(wkt)
        {
            reg = new Regex(bracketContent);
        }

        public WktParser():base(string.Empty) { }

        public List<Coordinate[]> FromReader()
        {
            int index = content.IndexOf('(');
            if (index < 0) throw new Exception("not recoginze wkt format");

            string type = content.Substring(0, index).Trim().ToUpper();
            geometryType = (GeoType)Enum.Parse(typeof(GeoType), type);

            var matchs = reg.Matches(content);
            switch (geometryType)
            {
                case GeoType.POINT:
                    return MultiPoint(matchs);
                case GeoType.LINESTRING:
                    return Linestring(matchs);
                case GeoType.POLYGON:
                    return Polygon(matchs);
                case GeoType.MULTIPOINT:
                    return MultiPoint(matchs);
                case GeoType.MULTILINESTRING:
                    return Linestring(matchs);
                case GeoType.MULTIPOLYGON:
                    return Polygon(matchs);
                case GeoType.GEOMETRYCOLLECTION:
                    throw new Exception("geometryType not supported"+geometryType);
                default: return null;
            }
        }

        public Geometry ReaderToGeometry()
        {
            var list = FromReader();
            return new Geometry(geometryType, list);
        }

        public string ToWriter(Geometry geometry)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(geometry.GeometryType.ToString());

            if (geometry.GeometryType == GeoType.POINT)
            {
                Coordinate point = geometry.GetSequence(0, 0)[0];
                builder.Append("(");
                builder.Append(point.To2DString(" "));
                builder.Append(")");
            }
            else if (geometry.GeometryType == GeoType.LINESTRING
                || geometry.GeometryType == GeoType.LineSegment)
            {
                var seqs = geometry.GetSequence(0, 0);
                builder.Append("(");
                builder.Append(string.Join(",", seqs.GetCoordinates().Select(x => x.To2DString(" "))));
                builder.Append(")");
            }
            else if (geometry.GeometryType == GeoType.POLYGON)
            {
                var seqs = geometry.GetGemoetry(0);
                builder.Append("(");

                for (int i = 0; i < seqs.Count; ++i)
                {
                    builder.Append("(");
                    builder.Append(string.Join(",", seqs[i].GetCoordinates().Select(x => x.To2DString(" "))));
                    builder.Append(")");
                    if (i < seqs.Count - 1) builder.Append(",");
                }
                builder.Append(")");
            }
            else if (geometry.GeometryType == GeoType.MULTIPOINT)
            {
                var seqs = geometry.GetSequence(0, 0);
                builder.Append("(");
                builder.Append(string.Join(",", seqs.GetCoordinates().Select(x => "(" + x.To2DString(" ") + ")")));
                builder.Append(")");
            }
            else if (geometry.GeometryType == GeoType.MULTILINESTRING)
            {
                var seqs = geometry.GetGemoetry(0);
                builder.Append("(");

                for (int i = 0; i < seqs.Count; ++i)
                {
                    builder.Append("(");
                    builder.Append(string.Join(",", seqs[i].GetCoordinates().Select(x => x.To2DString(" "))));
                    builder.Append(")");
                    if (i < seqs.Count - 1) builder.Append(",");
                }
                builder.Append(")");
            }
            else if (geometry.GeometryType == GeoType.MULTIPOLYGON)
            {
                builder.Append("(");
                for (int j = 0; j < geometry.GeometryCount; ++j)
                {
                    var seqs = geometry.GetGemoetry(j);
                    builder.Append("(");

                    for (int i = 0; i < seqs.Count; ++i)
                    {
                        builder.Append("(");
                        builder.Append(string.Join(",", seqs[i].GetCoordinates().Select(x => x.To2DString(" "))));
                        builder.Append(")");
                        if (i < seqs.Count - 1) builder.Append(",");
                    }

                    builder.Append(")");
                }
                builder.Append(")");
            }
            else throw new Exception("gemoetryType not supported" + geometry.GeometryType);

            return builder.ToString();
        }

        private List<Coordinate[]> Polygon(MatchCollection matchs)
        {
           return  MatchCollectionTo(matchs);
        }

        private List<Coordinate[]> MultiPoint(MatchCollection matchs)
        {
           return  MatchCollectionTo(matchs);
        }

        private List<Coordinate[]> Linestring(MatchCollection matchs)
        {
           return MatchCollectionTo(matchs);
        }

        private List<Coordinate[]> MatchCollectionTo(MatchCollection matchs)
        {
            List<Coordinate[]> coordinates = new List<Coordinate[]>(matchs.Count);

            foreach (var m in matchs)
            { 
                var geo = m.ToString().Split(comma);
                Coordinate[] coords = new Coordinate[geo.Length];

                for (int i = 0; i < coords.Length; ++i)
                {
                    string lonlat = geo[i];

                    var point = lonlat.Trim(' ').Split(space);
                    coords[i] = new Coordinate()
                    {
                        X = double.Parse(point[0]),
                        Y = double.Parse(point[1])
                    };
                }
                coordinates.Add(coords);
            }
            return coordinates;
        }
    }
}
